package data.Table;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import java.util.List;
import java.util.Map;



import data.meto.table;
import file_rw.FileRW;

public class Table implements TableInterface{
	
	
	public static String  filePath;

	public String   head_string ;
	public int     head_pageSize ; //default size
	public int     head_maxPayload ;
	public int     head_minPayload ;
	public int     head_firstFreeList ;
	public int     head_numOfFreeList ;
	public int     head_tableNum ;
	public int     head_tableOffset ;
	public int     head_overflow  ;	
	public static byte[] headByte = new byte[table_head_size];
	
	
	
	/////constructor 
	public Table(String filename){
		filePath = filename;
		readHead();
	}
	
	public Table(String filename,boolean check){
		filePath = filename;
	}
	


	
	
	///////get and set----------------------->


	public String getHead_string() {
		return head_string;
	}

	public void setHead_string(String head_string) {
		this.head_string = head_string;
	}

	public int getHead_pageSize() {
		return head_pageSize;
	}

	public void setHead_pageSize(int head_pageSize) {
		this.head_pageSize = head_pageSize;
	}

	public int getHead_maxPayload() {
		return head_maxPayload;
	}

	public void setHead_maxPayload(int head_maxPayload) {
		this.head_maxPayload = head_maxPayload;
	}

	public int getHead_minPayload() {
		return head_minPayload;
	}

	public void setHead_minPayload(int head_minPayload) {
		this.head_minPayload = head_minPayload;
	}

	public int getHead_firstFreeList() {
		return head_firstFreeList;
	}

	public void setHead_firstFreeList(int head_firstFreeList) {
		this.head_firstFreeList = head_firstFreeList;
	}

	public int getHead_numOfFreeList() {
		return head_numOfFreeList;
	}

	public void setHead_numOfFreeList(int head_numOfFreeList) {
		this.head_numOfFreeList = head_numOfFreeList;
	}

	public int getHead_tableNum() {
		return head_tableNum;
	}

	public void setHead_tableNum(int head_tableNum) {
		this.head_tableNum = head_tableNum;
	}

	public int getHead_tableOffset() {
		return head_tableOffset;
	}

	public void setHead_tableOffset(int head_tableOffset) {
		this.head_tableOffset = head_tableOffset;
	}

	public int getHead_overflow() {
		return head_overflow;
	}

	public void setHead_overflow(int head_overflow) {
		this.head_overflow = head_overflow;
	}
	
	public void read(){
		readHead();
	}
	
	
	//////////the method of head-------------------------------->

	/* (non-Java doc)
	 * Table initialization
	 * @see data.Table.TableInterface#ini()
	 */
	@Override
	public void ini() {
		head_string = "DBlite v0.1";
		head_pageSize = 1024; //default size
		head_maxPayload = 100;//100% used
		head_minPayload = 32;//25% used
		head_firstFreeList = 0;
		head_numOfFreeList = 0;
		head_tableNum  = 0;
		head_tableOffset = 40;
		head_overflow  = 0;
		writeHead();		
	}
	
	public void ini(int pagesize) {

		head_string = "DBlite v0.1";
		head_pageSize = pagesize; //default size

		head_maxPayload = 100;//100% used
		head_minPayload = 32;//25% used
		head_firstFreeList = 0;
		head_numOfFreeList = 0;
		head_tableNum  = 0;
		head_tableOffset = 40;
		head_overflow  = 0;
		writeHead();		
	}

	/* (non-Java doc)
	 * write the data to file
	 * @see data.Table.TableInterface#setHead()
	 */
	@Override
	public void setHead() {
		headChange2Byte();	
	}
	
	public String readStrFromByte(byte[] b ,int start,int n){

		byte[] tem = new byte[n];
		String temstr = "";
		for(int i =0;i<n;i++){
			tem[i] = b[start+i];
			temstr+=(char)b[start+i];
		}
		return temstr;		
	}
	
	public String readStrFromByte(byte[] b ,int start){
		String temstr = "";
		int n = (int)b[start];
		byte[] tem = new byte[n];
		for(int i =0;i<n;i++){
			tem[i] = b[start+i+1];
			temstr +=(char)b[start+i+1];
		}	
		return temstr;		
	}
	
	/**
	 * max of n is 4 ,that is we can convert byte(1-4) to integer
	 * @param b
	 * @param start
	 * @param n
	 * @return
	 */
	public int readIntFromByte(byte[] b,int start,int n){
		int res = 0;
		res = b[start];
		for(int i =1;i<n;i++){
			res = (res<<8)+b[start+i];
		}
		return res ;
	}
	
	public void setHeadFromByte(){
		head_string = readStrFromByte(headByte,0);
		head_pageSize = readIntFromByte(headByte,16,2); //default size
		head_maxPayload = readIntFromByte(headByte,18,1);//100% used
		head_minPayload = readIntFromByte(headByte,19,1);//25% used
		head_firstFreeList = readIntFromByte(headByte,20,4);
		head_numOfFreeList = readIntFromByte(headByte,24,4);
		head_tableNum  = readIntFromByte(headByte,28,4);
		head_tableOffset = readIntFromByte(headByte,32,4);
		head_overflow  = readIntFromByte(headByte,36,4);
	}
	
	/* (non-Java doc)
	 * read from the filePath,return the head (table_head_size byte);
	 * @see data.Table.TableInterface#readHead()
	 */
	@Override
	public void readHead() {
		FileRW fr = new FileRW();
		try {
			headByte = fr.readBytes(filePath, 0, table_head_size);
			setHeadFromByte();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/* (non-Java doc)
	 * write the head(byte[]) to the filePath
	 * @see data.Table.TableInterface#writeHead(byte[])
	 */
	@Override
	public void writeHead() {
		setHead();
		FileRW fw = new FileRW();
		try {
			fw.writeBytes(filePath, 0, headByte);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	/**
	 * add int (can be 1,2,3,4 byte) to byte[] head,from the start and length is len
	 * @param head
	 * @param add
	 * @param start
	 * @param len
	 * @return
	 */
	public void addHeadByte(int add,int start,int len){
		
		if(len == 1){
			byte temp = (byte)add;
			headByte[start] = temp;
		}else if(len == 2){
			byte[] temp = new byte[2];
			temp[0] = (byte)(add & 0xff);
			temp[1] = (byte) ((0xff00 & add) >> 8);
			headByte[start] = temp[1];
			headByte[start+1] = temp[0];
		}else if(len == 3){
			byte[] temp = new byte[3];
		    temp[0] = (byte) (0xff & add);  
		    temp[1] = (byte) ((0xff00 & add) >> 8);  
		    temp[2] = (byte) ((0xff0000 & add) >> 16);
		    headByte[start] = temp[2] ;
		    headByte[start+1] = temp[1];
		    headByte[start+2] = temp[0];
		}else if(len == 4){
			byte[] temp = new byte[4];
			temp = FileRW.int2byte(add);
			headByte[start] = temp[0] ;
			headByte[start+1] = temp[1];
			headByte[start+2] = temp[2];
			headByte[start+3] = temp[3];			
		}		
	}
	
	/**
	 * add string add to head from the start which length is len
	 * @param head
	 * @param add
	 * @param start
	 * @param len
	 * @return
	 * @throws Exception
	 */
	public void addHeadByte(String add,int start,int len) throws Exception{
		
		if(add.length()>(len-1)){
			throw new Exception("the head_string is too long"+"--max size is:"+table_head_string); 
		}else {
			int lenstr = add.length();
			headByte[start] = (byte)lenstr;
			for(int i =1;i<=lenstr;i++){
				headByte[i+start] = (byte)add.charAt(i-1);
			}		
		}
	}
	
	public byte[] addByte(byte[] b ,int add,int start,int len){
		
		if(len == 1){
			byte temp = (byte)add;
			b[start] = temp;
		}else if(len == 2){
			byte[] temp = new byte[2];
			temp[0] = (byte)(add & 0xff);
			temp[1] = (byte) ((0xff00 & add) >> 8);
			b[start] = temp[1];
			b[start+1] = temp[0];
		}else if(len == 3){
			byte[] temp = new byte[3];
		    temp[0] = (byte) (0xff & add);  
		    temp[1] = (byte) ((0xff00 & add) >> 8);  
		    temp[2] = (byte) ((0xff0000 & add) >> 16);
		    b[start] = temp[2] ;
		    b[start+1] = temp[1];
		    b[start+2] = temp[0];
		}else if(len == 4){
			byte[] temp = new byte[4];
			temp = FileRW.int2byte(add);
			b[start] = temp[0] ;
			b[start+1] = temp[1];
			b[start+2] = temp[2];
			b[start+3] = temp[3];			
		}	
		return b;
	}
	
	public byte[] addByte(byte[] b ,String add,int start){
	
		int lenstr = add.length();
		for(int i =0;i<lenstr;i++){
			b[i+start] = (byte)add.charAt(i);
		}		
		return b;
	}
	
	/**
	 * the number in the method should be table_head_xx, for short ,we use the number.
	 * it will be big problem to change the format of table_head.
	 * we will modify it in the future.
	 * @return
	 */
	public void headChange2Byte() {
		
		//head_string to byte
		try {
			addHeadByte(head_string,0,16);
		} catch (Exception e) {
			e.printStackTrace();
		}
		//head_pageSize to byte
		addHeadByte(head_pageSize,16,2);	
		//head_maxPayload to byte
		addHeadByte(head_maxPayload,18,1);
		//head_minPayload to byte
		addHeadByte(head_minPayload,19,1);
		//head_firstFreeList to byte
		addHeadByte(head_firstFreeList,20,4);
		//head_numOfFreeList to byte
		addHeadByte(head_numOfFreeList,24,4);
		//head_tableNum to byte
		addHeadByte(head_tableNum,28,4);
		//head_tableOffset to byte
		addHeadByte(head_tableOffset,32,4);
		//head_overflow to byte
		addHeadByte(head_overflow,36,4);		
	}
	
	public int readFirstFreeList(int pagenum){
		
		int res = 0;
		FileRW fr = new FileRW();
		try {
			res = fr.readInt(filePath, head_pageSize*(pagenum-1)+table_head_size);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return res;
	}
	
	public int getEmptyPage(){

			FileRW fr = new FileRW();
			long num = 0;
			num = fr.getFileLen(filePath);
			int tem = (int) (num / 1024);
			fr.appendBytes(filePath, new byte[table_page_defaultsize]);		
			Page newroot = new Page(tem+1,true);
			newroot.ini(table_page_type_btreepleaf);
			return tem+1;
	}
	
	public int getEmptyPage(boolean check){
		if(head_firstFreeList!=0){
			int tem = head_firstFreeList;
			head_firstFreeList = readFirstFreeList(head_firstFreeList);
			return tem;
		}else {
			FileRW fr = new FileRW();
			long num = 0;
			num = fr.getFileLen(filePath);
			int tem = (int) (num / 1024);
			fr.appendBytes(filePath, new byte[table_page_defaultsize]);		
			Page newroot = new Page(tem+1,true);
			newroot.ini(true);
			return tem+1;
		}
	}
	
	/**
	 * write the byte[] b  to the overflow page...now ,have no time to write the code..
	 * to be continued...
	 * @param pagenum
	 * @param b
	 */
	public void setHeadOverflowPage(int pagenum,byte[] b){
		int offset = table_head_size+(pagenum-1)*table_page_defaultsize;

		int off = 0;
		int overflow = 0;
		byte[] page = new byte[table_page_defaultsize];
		FileRW fr = new FileRW();
		try {
			page = fr.readBytes(filePath, offset, table_page_defaultsize);
			byte[] temp = new byte[4];
			temp[0] = 0;
			temp[1] = 0;
			temp[2] = page[0];
			temp[3] = page[1];
			int num = FileRW.byte2int(temp);
			temp[0] = page[2];
			temp[1] = page[3];
			temp[2] = page[4];
			temp[3] = page[5];
			off = FileRW.byte2int(temp);			
			temp[0] = page[6];
			temp[1] = page[7];
			temp[2] = page[8];
			temp[3] = page[9];
			overflow = FileRW.byte2int(temp);
			if(overflow!=0||(off+b.length>table_page_defaultsize)){
				setHeadOverflowPage(overflow,b);
			}else{
				num++;
				if(off==0){
					off = 10;
				}
				temp = FileRW.int2byte(num);
				page[0] = temp[2];
				page[1] = temp[3];				
				temp = FileRW.int2byte(off+b.length);
				page[2] = temp[0];
				page[3] = temp[1];
				page[4] = temp[2];
				page[5] = temp[3];
				
				for(int i=0;i<b.length;i++){
					page[i+off] = b[i];
				}
				fr.writeBytes(filePath, offset, page);
			}
		} catch (FileNotFoundException e) {

			e.printStackTrace();
		}
		
	}

	public int[] getOverflowTableNum(int pagenum){
		byte[] temp = new byte[4];
		byte[] page = new byte[table_page_defaultsize];
		int[] num = new int[2];
		FileRW fr = new FileRW();
		try {
			page = fr.readBytes(filePath, head_pageSize*(pagenum-1)+table_head_size,table_page_defaultsize );
			temp[0] = 0;
			temp[1] = 0;
			temp[2] = page[0];
			temp[3] = page[1];
			num[0] = FileRW.byte2int(temp);
			temp[0] = page[6];
			temp[1] = page[7];
			temp[2] = page[8];
			temp[3] = page[9];
			num[1] = FileRW.byte2int(temp);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return num;
	}
	
	/**
	 * get the table map from overflow page
	 * @param pagenum
	 * @return
	 */
	public Map<String,table> getTableFromOverflow(int pagenum){
		Map<String,table> resmap = new HashMap<String,table>();
		byte[] overFlowByte = new byte[table_page_defaultsize];
		FileRW fr = new FileRW();
		int  numOfCells = 0;
		int  offsets = 10;

		int temlen = 0;
		try {
			overFlowByte = fr.readBytes(filePath,head_pageSize*(pagenum-1)+table_head_size, table_page_defaultsize);
			numOfCells = readIntFromByte(overFlowByte,0,2);

			for(int i=0;i<numOfCells;i++){
				table  tb = new table();
				temlen = readIntFromByte(overFlowByte,offsets,2);
				String tn = readStrFromByte(overFlowByte,offsets+10,readIntFromByte(overFlowByte,offsets+8,2));

				tb.setTableName(tn);
				tb.setElementNum(0);
				tb.setRootPageNum(readIntFromByte(overFlowByte,offsets+2,4));
				tb.setTableNum(readIntFromByte(overFlowByte,offsets+6,2));
				List<String> elements = new ArrayList<String>();
				List<String> types = new ArrayList<String>();
				String tempele = "";
				String temptype ="";
				int offset = offsets ;
				
				for(int j=0;j<tb.getTableNum();j++){
					tempele = readStrFromByte(overFlowByte,offset+tn.length()+10+2+2,readIntFromByte(overFlowByte,offset+10+tn.length()+2,2));
					int inttype = readIntFromByte(overFlowByte,offset+10+tn.length(),2);
					if(inttype == 1){
						temptype = "int" ;
					}else if(inttype ==2){
						temptype = "long";
					}else if(inttype == 4){
						temptype ="double";
					}else {
						temptype = "text";
					}
					elements.add(tempele);
					types.add(temptype);
					offset = offset + 2+2+tempele.length();
				}
				tb.setElements(elements);
				tb.setTypes(types);
				offsets = offsets + temlen ;
				resmap.put(tn, tb);
			}		
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return resmap;
	}
	
	/**
	 * get the detail information of table(tbName) from the filePath
	 * @param tbName
	 * @return
	 * @throws 
	 */
	public Map<String,table> getTables() {

		Map<String,table> resmap = new HashMap<String,table>();
		if(head_overflow!=0){
			resmap = getTableFromOverflow(head_overflow);
		}
		int resnum = resmap.size();
		int offset = 40;
		int len = 0 ;
		FileRW fr = new FileRW();
		for(int i=0;i<head_tableNum-resnum;i++){
			table res_tb = new table();
			List<String> elements = new ArrayList<String>();
			List<String> types = new ArrayList<String>();	
			len = readIntFromByte(headByte,offset,2);
			String tempstr = fr.readStrFromByte(headByte, offset+10,readIntFromByte(headByte,offset+8,2));
			res_tb.setRootPageNum(readIntFromByte(headByte,offset+2,4));
			res_tb.setElementNum(0);    //////have no the type
			res_tb.setTableName(tempstr);
			res_tb.setTableNum(readIntFromByte(headByte,offset+6,2));
			int offsets = offset ;
			if(res_tb.getTableNum()!=0){
				String tempstrs = "";
				int temptype = 0 ;
				for(int j=0;j<res_tb.getTableNum();j++){
					int tes = readIntFromByte(headByte,offsets+10+tempstr.length()+2,2);
					tempstrs = readStrFromByte(headByte,offsets+10+tempstr.length()+2+2,tes);
					temptype = readIntFromByte(headByte,offsets+10+tempstr.length(),2);
					elements.add(tempstrs);
					if(temptype == 1){
						types.add("int");
					}else if(temptype == 2){
						types.add("long");
					}else if(temptype == 4){
						types.add("double");
					}else{
						types.add("text");
					}						
					offsets = offsets + 2 + 2+tempstrs.length();
				}
			}
			res_tb.setElements(elements);
			res_tb.setTypes(types);
			offset = offset + len ;
			resmap.put(tempstr, res_tb);
		}	
		return resmap;
		
	}
	
	public List<Integer> getEmptyPages(){
		List<Integer> res = new ArrayList<Integer>();
		int tem = head_numOfFreeList;
		int tem_free = head_firstFreeList;
		if(tem>0){
			res.add(tem_free);
			tem--;
			if(tem>0){
				FileRW fr = new FileRW();
				try {
					tem_free = fr.readInt(filePath, table_head_size+(tem_free-1)*table_page_defaultsize);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}			
		}		
		return res;
	}
	
	
	
	
	/**
	 * we use the ... for unknown args
	 * @param table
	 * @param n
	 * @param args
	 */
	public byte[] setTableByte(String table,String[] args,String[] type){

		int n = args.length;
		int len = (n+1)*table_head_ch_elsize+table_head_ch_size+table_head_ch_rootnum+table_head_ch_argsnum+table.length()+n*2;
		for(int i=0;i<n;i++){
			len+=args[i].length();
		}
		byte[] b = new byte[len];
		b = addByte(b,len,0,2);		
		int tems = getEmptyPage();
		b = addByte(b,tems,2,4);		
		b = addByte(b,args.length,6,2);
		b = addByte(b,table.length(),8,2);
		b = addByte(b,table,10);
		int last = 10+table.length();
		for(int j=0;j<args.length;j++){
			int tem = args[j].length();
			b = addByte(b,tem,last+2,2);
			if(type[j]=="int"){
				b = addByte(b,1,last,2);
			}else if(type[j]=="long"){
				b = addByte(b,2,last,2);
			}else if(type[j]=="double"){
				b = addByte(b,4,last,2);
			}else if(type[j]=="text"){
				b = addByte(b,8,last,2);
			}
			b = addByte(b,args[j],last+4);
			last += 4 +tem; 
		}
		return b;
	}
	
	public void writeTableByte(String table,String ...args){
		
		String[] type = new String[args.length/2];
		String[] name = new String[args.length/2];
		int j = 0;
		int k = 0;
		for(int i =0;i<args.length;i++){
			if(i%2==0){
				name[j++] = args[i];
			}else
			{
				type[k++] = args[i];
			}
		}	
		
		byte[] b = setTableByte(table,name,type);
		if(head_overflow==0){
			if(head_tableOffset+b.length>table_head_size){
				if(b.length>head_pageSize){
				System.out.println("oh!..your table' length is more than "+head_pageSize+" !!!,you are too gridy!");
				}else{
					head_overflow = getEmptyPage(true);
					setHeadOverflowPage(head_overflow,b); 
				}
			}else{
				int start = head_tableOffset;
				head_tableOffset += b.length; 
				for(int i=0;i<b.length;i++){
					headByte[start++] = b[i];
				}
			}
		}else {
			setHeadOverflowPage(head_overflow,b); 
		}
		head_tableNum ++;
	}
	
	///////////////////below is the method of btree+------------------->
	


}
